

package com.gitee.huanminabc.jcommon.test;

import com.gitee.huanminabc.jcommon.base.SnowIdUtil;
import com.gitee.huanminabc.jcommon.multithreading.executor.SleepTools;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;

import java.text.NumberFormat;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 统计多任务执行时间 ,多线程模式
 */
@Data
public class TaskTimeWatch {

    /**
     * 此 TaskTimeWatch的标识符 。
     * 当我们有来自多个秒表的输出并需要在 log 或 console 输出中区分它们时，这很方便
     */
    private final String taskGroupName;
    @Setter
    private String taskGroupNameDesc = "";


    private final List<TaskInfo> taskList = new CopyOnWriteArrayList<>();

    private Map<String, TaskThreadCurrentInfo> taskThreadCurrentInfoMap = new ConcurrentHashMap<>();

    private ThreadLocal<String> currentTaskName = new ThreadLocal<>();


    private volatile TaskInfo lastTaskInfo;

    private AtomicInteger taskCount = new AtomicInteger();
    private AtomicInteger off = new AtomicInteger();
    //总等待超时时间,默认5秒
    private Long totalWaitTime;
    //任务的创建时间, 用于判断stop和stop 没有正确关闭,延迟等待
    private Long startTime;

    /**
     * 总运转时间
     */
    private AtomicLong totalTimeNanos = new AtomicLong();

    public TaskTimeWatch() {
        this(SnowIdUtil.uniqueLongHex());
    }

    public TaskTimeWatch(String id) {
        this(id, TimeUnit.SECONDS.toMillis(5));
    }
    public TaskTimeWatch(String id,Long totalWaitTime) {
        this.taskGroupName = id;
        this.startTime = System.currentTimeMillis();
        this.totalWaitTime = totalWaitTime;
    }

    //合并TaskTimeWatch
    public TaskTimeWatch merge(TaskTimeWatch... TaskTimeWatch) {
        for (TaskTimeWatch threadTimeWatch : TaskTimeWatch) {
            this.taskCount.addAndGet(threadTimeWatch.getTaskCount());
            this.totalTimeNanos.addAndGet(threadTimeWatch.getTotalTimeNanos());
            this.taskList.addAll(threadTimeWatch.getTaskList());
        }
        return this;
    }

    /**
     * 启动命名任务。
     * 如果在 stop() 未先调用此方法的情况下调用 or timing 方法，则结果是 undefined 的。
     *
     * @param taskName the name of the task to start
     */
    public void start(String taskName) throws IllegalStateException {
        TaskThreadCurrentInfo taskThreadCurrentInfo = new TaskThreadCurrentInfo();
        taskThreadCurrentInfo.startTimeNanos = System.nanoTime();
        taskThreadCurrentInfo.currentTaskName = taskName;
        taskThreadCurrentInfo.status = true;
        taskThreadCurrentInfoMap.put(taskName, taskThreadCurrentInfo);
        currentTaskName.set(taskName);
        off.incrementAndGet();
    }

    /**
     * 停止当前任务。必须和start在同一个线程中调用。
     * 如果在未调用至少一对 start() / stop() 方法的情况下调用 timing methods，则结果是 undefined 的。
     */
    public void stop() throws IllegalStateException {
        String taskName = currentTaskName.get();
        if (taskName == null) {
            throw new IllegalStateException("请使用start后才能使用stop。");
        }
        TaskThreadCurrentInfo taskThreadCurrentInfo = taskThreadCurrentInfoMap.get(taskName);
        taskThreadCurrentInfo.status = false;
        long lastTime = System.nanoTime() - taskThreadCurrentInfo.startTimeNanos;
        this.totalTimeNanos.addAndGet(lastTime);
        this.lastTaskInfo = new TaskInfo(taskName, lastTime);
        this.taskList.add(this.lastTaskInfo);
        this.taskCount.incrementAndGet();
        this.currentTaskName.remove();
        off.decrementAndGet();

    }

    /**
     * 确定此 Web TaskTimeWatch 站点当前是否正在运行。
     *
     * @see #currentTaskName()
     */
    public boolean isRunning() {
        return (this.currentTaskName.get() != null);
    }

    /**
     * Get the name of the currently running task, if any.
     *
     * @see #isRunning()
     * @since 4.2.2
     */
    public String currentTaskName() {
        return this.currentTaskName.get();
    }

    /**
     * Get the time taken by the last task in nanoseconds.
     *
     * @see #getLastTaskTimeMillis()
     * @since 5.2
     */
    public long getLastTaskTimeNanos() throws IllegalStateException {
        if (this.lastTaskInfo == null) {
            throw new IllegalStateException("No tasks run: can't get last task interval");
        }
        return this.lastTaskInfo.getTimeNanos();
    }

    /**
     * Get the time taken by the last task in milliseconds.
     *
     * @see #getLastTaskTimeNanos()
     */
    public long getLastTaskTimeMillis() throws IllegalStateException {
        if (this.lastTaskInfo == null) {
            throw new IllegalStateException("No tasks run: can't get last task interval");
        }
        return this.lastTaskInfo.getTimeMillis();
    }

    /**
     * Get the name of the last task.
     */
    public String getLastTaskName() throws IllegalStateException {
        if (this.lastTaskInfo == null) {
            throw new IllegalStateException("No tasks run: can't get last task name");
        }
        return this.lastTaskInfo.getTaskName();
    }

    /**
     * Get the last task as a {@link TaskInfo} object.
     */
    public TaskInfo getLastTaskInfo() throws IllegalStateException {
        if (this.lastTaskInfo == null) {
            throw new IllegalStateException("No tasks run: can't get last task info");
        }
        return this.lastTaskInfo;
    }


    /**
     * Get the total time in nanoseconds for all tasks.
     *
     * @see #getTotalTimeMillis()
     * @see #getTotalTimeSeconds()
     * @since 5.2
     */
    public long getTotalTimeNanos() {
        return this.totalTimeNanos.get();
    }

    /**
     * Get the total time in milliseconds for all tasks.
     *
     * @see #getTotalTimeNanos()
     * @see #getTotalTimeSeconds()
     */
    public long getTotalTimeMillis() {
        return nanosToMillis(this.totalTimeNanos.get());
    }

    /**
     * Get the total time in seconds for all tasks.
     *
     * @see #getTotalTimeNanos()
     * @see #getTotalTimeMillis()
     */
    public double getTotalTimeSeconds() {
        return nanosToSeconds(this.totalTimeNanos.get());
    }

    /**
     * Get the number of tasks timed.
     */
    public int getTaskCount() {
        return this.taskCount.get();
    }

    /**
     * Get an array of the data for tasks performed.
     */
    public TaskInfo[] getTaskInfo() {
        return this.taskList.toArray(new TaskInfo[0]);
    }


    /**
     * Get a short description of the total running time.
     */
    public String shortSummary() {
        return "TaskTimeWatch '" + getTaskGroupName() + "': running time = " + getTotalTimeMillis() + " ms";
    }

    //如果off.get() != 0，则说明有任务没有执行完, 判断等待时间然后等待
    public void waitToFinish() {
        while (off.get() != 0) {
            if (totalWaitTime != null) {
                //判断时间
                if (System.currentTimeMillis()-this.startTime  > totalWaitTime) {
                    throw new IllegalStateException("请检查start和stop的调用是否成对,或者总时间超时");
                }
                SleepTools.ms(10);
            } else {
                throw new IllegalStateException("请检查start和stop的调用是否成对,或者总时间超时");
            }
        }
    }

    /**
     * 生成一个字符串，其中包含一个描述已执行的所有任务的表。
     * 对于自定义报告，请直接调用 getTaskInfo() 并使用任务信息。
     */
    public String prettyPrint() {
        waitToFinish();
        StringBuilder sb = new StringBuilder(shortSummary());
        //如果有描述
        if (!taskGroupNameDesc.isEmpty()) {
            sb.append("\n----------- ").append(taskGroupNameDesc).append(" -----------");
        }
        sb.append('\n');
        sb.append("--------------------Total tasks:").append(taskCount).append("-------------------------\n");
        sb.append("ms         %     Task name\n");
        sb.append("---------------------------------------------\n");
        NumberFormat nf = NumberFormat.getNumberInstance();
        nf.setMinimumIntegerDigits(9);
        nf.setGroupingUsed(false);
        NumberFormat pf = NumberFormat.getPercentInstance();
        pf.setMinimumIntegerDigits(2);
        pf.setGroupingUsed(false);
        for (TaskInfo task : getTaskInfo()) {
            sb.append(nf.format(task.getTimeMillis())).append("  ");
            sb.append(pf.format((double) task.getTimeMillis() / getTotalTimeMillis())).append("  ");
            sb.append(task.getTaskName()).append('\n');
        }
        return sb.toString();
    }

    /**
     * Generate an informative string describing all tasks performed
     * <p>For custom reporting, call {@link #getTaskInfo()} and use the task info
     * directly.
     */
    @Override
    public String toString() {
        //如果数量不是偶数那么就有问题
        waitToFinish();
        StringBuilder sb = new StringBuilder(shortSummary());
        for (TaskInfo task : getTaskInfo()) {
            sb.append("; [").append(task.getTaskName()).append("] took ").append(task.getTimeMillis()).append(" ms");
            long percent = Math.round(100.0 * task.getTimeMillis() / getTotalTimeMillis());
            sb.append(" = ").append(percent).append('%');
        }
        return sb.toString();
    }


    private static long nanosToMillis(long duration) {
        return TimeUnit.NANOSECONDS.toMillis(duration);
    }

    private static double nanosToSeconds(long duration) {
        return duration / 1_000_000_000.0;
    }


    private static class TaskThreadCurrentInfo {
        //任务启动的时间
        private long startTimeNanos;
        //任务的名称
        private String currentTaskName;
        //状态 true表示开始 false表示结束
        private boolean status;
    }

    @Getter
    public static final class TaskInfo {
        /**
         * -- GETTER --
         * Get the name of this task.
         */
        private final String taskName;

        /**
         * -- GETTER --
         * Get the time in nanoseconds this task took.
         */
        private final long timeNanos;

        TaskInfo(String taskName, long timeNanos) {
            this.taskName = taskName;
            this.timeNanos = timeNanos;
        }

        /**
         * Get the time in milliseconds this task took.
         *
         * @see #getTimeNanos()
         * @see #getTimeSeconds()
         */
        public long getTimeMillis() {
            return nanosToMillis(this.timeNanos);
        }

        /**
         * Get the time in seconds this task took.
         *
         * @see #getTimeMillis()
         * @see #getTimeNanos()
         */
        public double getTimeSeconds() {
            return nanosToSeconds(this.timeNanos);
        }

    }

}
